nRF Connect DFU 내용 정리

2025. 6. 4(SW개발실)

□ 주요 설명

o Zephyr 기반의 부트로더인 MCUBOOT를 기반하여 DFU(Device Firmware Update) 기능 구현

o 기기 내 플래시는 부트로더(MCUBoot), 펌웨어가 실행되는 primary slot(slot0), 그리고 업데이트 이미지를 저장하는 secondary slot(slot1)으로 구성되어 있음

o 플래시 용량 활용을 극대화하기 위해, MCUBoot 소스코드를 커스터마이즈하여 임의의 플래시 주소에 데이터를 기록할 수 있는 기능 추가

□ 파일 설명

o MCUBOOT 소스코드

- C:\ncs\v2.9.1\bootloader\mcuboot\boot\zephyr\Kconfig

- C:\ncs\v2.9.1\bootloader\mcuboot\boot\bootutil\src\loader.c

o 프로젝트

- 프로젝트 폴더 및 파일 계층 구조

프로젝트 root

├── src

│    ├── main.c

│    ├── sys_ble.cpp

│    ├── sys_ble.h

│    ├── sys_mcuboot.cpp
│    └── sys_mcuboot.h

│

├── sysbuild
│    └── mcuboot.conf

│

├── prj.conf

├── sysbuild.conf

├── VERSION
└── CMakeLists.txt

□ MCUBOOT 소스코드 폴더 내 선언 및 설정

[Kconfig]

- C:\ncs\v2.9.1\bootloader\mcuboot\boot\zephyr\Kconfig 파일에 커스텀 매크로 추가

[loader.c]

- C:\ncs\v2.9.1\bootloader\mcuboot\boot\bootutil\src\loader.c 소스코드 내의
boot_copy_image 함수 수정

→ MCUBOOT_SAFEWARE_DUAL 매크로가 정의되어 있으면, secondary slot(slot1)에 위치한 펌웨어 이미지를 특정 시작 주소에 복사하도록 동작을 수정

1. MCUBOOT_SAFEWARE_DUAL 매크로 확인

→ slot 1의 버전 정보를 통해 시작 주소 offset 확인, 펌웨어의 전체 사이즈 계산

#if defined(CONFIG_MCUBOOT_SAFEWARE_DUAL)

BOOT_LOG_INF("MCUBOOT_SAFEWARE_DUAL");

rc = flash_area_open(FLASH_AREA_IMAGE_SECONDARY(image_index),

&fap_secondary_slot);

assert (rc ==0);

uint32_t  Dual_offset; // SafeWare dual firmware offset

uint16_t  Dual_size; // SafeWare dual firmware size

int DUAL_MODE =0;

struct image_header hdr;

struct image_tlv_info tlv_hdr;

rc = flash_area_read(fap_secondary_slot, 0, &tlv_hdr, sizeof(tlv_hdr));

if (rc !=0) {

BOOT_LOG_INF("Failed to read image TLV info: %d", rc);

return -1;

}

uint16_t tlv_size = tlv_hdr.it_tlv_tot;

rc = flash_area_read(fap_secondary_slot, 0, &hdr, sizeof(hdr));

if (rc !=0) {

BOOT_LOG_INF("Failed to read image header: %d", rc);

return -1;

}

if (!(hdr.ih_ver.iv_build_num ==0)) {

DUAL_MODE =1;

Dual_offset = hdr.ih_ver.iv_build_num; // SafeWare dual firmware offset

Dual_size = hdr.ih_hdr_size + hdr.ih_img_size + tlv_size;

BOOT_LOG_INF("SafeWare dual firmware detected.");

}

else {

DUAL_MODE =0;

}

#endif

2. erase 부분 추가

→ offset에 해당되는 주소가 존재하는 섹션부터 펌웨어 사이즈가 들어갈 섹션까지만 erase 실행

(추가된 부분)

sect_count = boot_img_num_sectors(state, BOOT_PRIMARY_SLOT);

#if defined(CONFIG_MCUBOOT_SAFEWARE_DUAL)

if (DUAL_MODE ==1) {

BOOT_LOG_INF("Dual firmware erase");

uint32_t erased =0;

BOOT_LOG_INF("Dual firmware erase offset: 0x%08x, size: 0x%08x", Dual_offset,               Dual_size);

uint32_t erase_start = Dual_offset &0xFFFFF000;

uint32_t erase_end   = (Dual_offset + Dual_size +0xFFF) &0xFFFFF000;

uint32_t erase_size  = erase_end - erase_start;

rc = boot_erase_region(fap_primary_slot, erase_start, erase_size);

assert(rc ==0);

BOOT_LOG_INF("Erase region: 0x%08x ~ 0x%08x", erase_start, erase_end -1);

}

(기존 부분)

#else

BOOT_LOG_INF("No dual firmware erase needed");

for (sect =0, size =0; sect < sect_count; sect++) {

this_size = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, sect);

rc = boot_erase_region(fap_primary_slot, size, this_size);

assert(rc ==0);

#if defined(MCUBOOT_OVERWRITE_ONLY_FAST)

if ((size + this_size) >= src_size) {

size += src_size -size;

size += BOOT_WRITE_SZ(state) - (size % BOOT_WRITE_SZ(state));

break;

}

size += this_size;

}

#endif

#endif

3. copy 부분 추가

→ 펌웨어의 시작 주소와 사이즈 만큼 copy 진행

(추가된 부분)

#if defined(CONFIG_MCUBOOT_SAFEWARE_DUAL)

if (DUAL_MODE ==1) {

BOOT_LOG_INF("Copying secondary slot to primary slot: src_offset=0, dst_offset=0x%08x, size=0x%08x",

Dual_offset, Dual_size);

rc = boot_copy_region(state, fap_secondary_slot, fap_primary_slot, 0, Dual_offset, Dual_size);

}

(기존 부분)

#else

BOOT_LOG_INF("Copying secondary slot to primary slot: src_offset=0, dst_offset=0, size=0x%zx", size);

rc = boot_copy_region(state, fap_secondary_slot, fap_primary_slot, 0, 0, size);

#endif

□ 프로젝트 폴더 내 선언 및 설정

o 프로젝트 sysbuild.conf 파일 생성 및 선언 추가

[sysbuild.conf] 프로젝트 root 폴더에 sysbuild.conf 파일 생성

- SB_CONFIG_BOOTLOADER_MCUBOOT
전체 sysbuild 이미지에서 MCUBoot 부트로더를 빌드/포함하도록 지정

- SB_CONFIG_BOOT_SIGNATURE_KEY_FILE
부트로더가 **이미지 검증(서명 검증)**에 사용할 RSA 개인키(.pem) 경로 지정

- SB_CONFIG_MCUBOOT_MODE_OVERWRITE_ONLY
MCUBoot 부트로더를 overwrite only 모드(스왑/롤백 없이 slot0만 덮어쓰기)로 동작하게 설정

o 프로젝트 prj.conf 파일에 선언 추가

[prj.conf] 프로젝트 root 폴더에 prj.conf 파일 생성

- CONFIG_NCS_SAMPLE_MCUMGR_BT_OTA_DFU
BLE OTA(무선 펌웨어 업데이트)를 위한 MCUmgr/SMP 기능 활성화

o 프로젝트 mcuboot.config 커스텀 매크로 선언

[mcuboot.conf] 프로젝트 root\sysbuild 폴더에 mcuboot.conf 매크로 선언

- 부트로더가 매크로를 인식해서 동작

o VERSION 파일 생성 및 Cmakelist.txt 설정

[version]

- 펌웨어 버전 설정을 위한 VERSION 추가

o imgtool을 통한 서명 및 nrfjprog를 통한 검증

[imgtool sign] imgtool sign --header-size 512 --pad-header --version 1.2.3+222976 --slot-size 0x80000 --key "C:\nRF\safeware_private_key_root-rsa-2048.pem" --public-key-format hash <서명 전 파일> <서명 후 생성될 파일>

- 빌드시 생성된 zephyr.bin 파일에 imgtool sign 명령어를 사용하여 서명 데이터 추가

→ header size: 1번 펌웨어의 경우 2048의 헤더사이즈 고정 필요

→ version: major. minor. patch level + TWEAK
이때 TWEAK 값을 통해 2번 펌웨어의 시작 주소를 설정(222976 = 0x36700)
1번 펌웨어는 0으로 설정해야함

→ key: sysbuild에서 사용한 서명키와 동일하게 설정해야 DFU 가능

[nrfjprog —-verify]  nrfjprog —verify <펌웨어파일.hex>

- nrfjprog의 verify 명령어를 통해, 생성된 펌웨어(hex)가 실제 플래시에 올바르게 기록되었는지 검증할 수 있음

- z2o_hex 프로그램을 통해 각 펌웨어(bin)를 시작 주소에 0xC000을 더한 값으로 hex파일 변환
1번 펌웨어 시작 주소 예시: 0 + 0xC000
2번 펌웨어 시작 주소 예시: 0x36700 + 0xc000 = 0x42700

o 업데이트 관리를 위한 코드 sys_mcuboot

- 요약표

함수 이름	함수 기능 요약	설명
sys_mcuboot_slot1_check	Slot1 펌웨어 버전 검사	slot1 플래시 헤더 읽고, 현재 펌웨어보다 버전 높으면 1 반환
sys_mcuboot_upgrade	Slot1 버전이 높으면 업그레이드 예약	slot1에 최신 이미지 있으면 업그레이드 예약
sys_mcuboot_init	MCUBoot 관련 초기화(Upgrade)	부팅 시 업그레이드 시도
sys_mcuboot_version_print	현재 펌웨어 버전 정보 출력	현재 실행중인 펌웨어의 버전(major, minor, patch, tweak) 출력

- 함수 설명

[sys_mcuboot_slot1_check]

→ DFU용 slot1(secondary flash)에 저장된 펌웨어 헤더를 읽어 이미지 유효성(magic number) 확인slot1의 펌웨어 버전이 현재 구동 버전보다 높은지 비교
1 : slot1의 버전이 높음(업그레이드 필요)
0 : slot1의 버전이 같거나 낮거나, 무효

[sys_mcuboot_upgrade]

→ sys_mcuboot_slot1_check()로 업그레이드 필요시

→ boot_request_upgrade()로 다음 리부트 시 업그레이드(스왑) 예약

[sys_mcuboot_init]

→ 부트/시작 시 한 번 호출하는 MCUBoot 관련 초기화 함수

[sys_mcuboot_version_print]

→ 현재 실행중인 펌웨어의 버전 정보를 로그로 출력